#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <assert.h>
#include <pthread.h>
#include <time.h>
#include "../src/transaction/transaction_manager.h"
#include "../src/utils/logger.h"
#include "../include/gudb.h"

// Test configuration
#define NUM_TEST_THREADS 4
#define NUM_TRANSACTIONS_PER_THREAD 10
#define LOCK_TIMEOUT_MS 5000

// Global test variables
transaction_manager_t* g_tm = NULL;
logger_t* g_logger = NULL;

void setup_test_environment() {
    printf("Setting up test environment...\n");
    
    // Initialize logger
    logger_config_t logger_config = logger_default_config();
    strcpy(logger_config.log_file_path, "test_transaction_acid.log");
    logger_config.enable_console_output = false;
    
    g_logger = logger_create(&logger_config);
    assert(g_logger != NULL);
    logger_init(g_logger);
    logger_set_global(g_logger);
    
    // Create transaction manager
    g_tm = txn_manager_create();
    assert(g_tm != NULL);
    
    // Enable deadlock detection
    g_tm->enable_deadlock_detection = true;
    g_tm->lock_timeout_ms = LOCK_TIMEOUT_MS;
    
    printf("Test environment setup complete!\n");
}

void cleanup_test_environment() {
    printf("Cleaning up test environment...\n");
    
    if (g_tm) {
        txn_manager_destroy(g_tm);
        g_tm = NULL;
    }
    
    if (g_logger) {
        logger_destroy(g_logger);
        g_logger = NULL;
    }
    
    printf("Test environment cleanup complete!\n");
}

void test_transaction_lifecycle() {
    printf("Testing transaction lifecycle...\n");
    
    // Test transaction begin
    transaction_t* txn = txn_begin(g_tm, ISOLATION_READ_COMMITTED);
    assert(txn != NULL);
    assert(txn->state == TXN_ACTIVE);
    assert(txn->isolation_level == ISOLATION_READ_COMMITTED);
    assert(txn->xid > BOOTSTRAP_TXN_ID);
    
    printf("Transaction %llu created successfully\n", txn->xid);
    
    // Test transaction lookup
    transaction_t* found_txn = txn_get_by_id(g_tm, txn->xid);
    assert(found_txn == txn);
    
    // Test transaction status
    assert(txn_is_active(g_tm, txn->xid) == true);
    assert(txn_is_committed(g_tm, txn->xid) == false);
    assert(txn_is_aborted(g_tm, txn->xid) == false);
    
    // Save transaction ID before commit (commit will free the transaction)
    txn_id_t xid = txn->xid;
    
    // Test transaction commit
    int result = txn_commit(g_tm, txn);
    assert(result == 0);
    
    // Verify commit status using saved ID
    assert(txn_is_committed(g_tm, xid) == true);
    assert(txn_is_active(g_tm, xid) == false);
    
    printf("Transaction lifecycle test passed!\n");
}

void test_transaction_abort() {
    printf("Testing transaction abort...\n");
    
    // Create transaction
    transaction_t* txn = txn_begin(g_tm, ISOLATION_READ_COMMITTED);
    assert(txn != NULL);
    
    txn_id_t xid = txn->xid;
    printf("Testing abort for transaction %llu\n", xid);
    
    // Add some undo log entries
    char old_data[] = "test data for undo";
    int result = txn_add_undo_log(txn, UNDO_UPDATE, 1, 123, 
                                 old_data, sizeof(old_data), 100);
    assert(result == 0);
    assert(txn->undo_log_count == 1);
    
    // Test transaction abort
    result = txn_abort(g_tm, txn);
    assert(result == 0);
    
    // Note: txn pointer is now invalid after abort
    // Verify abort status using transaction ID
    assert(txn_is_aborted(g_tm, xid) == true);
    assert(txn_is_active(g_tm, xid) == false);
    
    printf("Transaction abort test passed!\n");
}

void test_lock_compatibility_matrix() {
    printf("Testing lock compatibility matrix...\n");
    
    // Test all lock mode combinations
    struct {
        lock_mode_t mode1;
        lock_mode_t mode2;
        bool expected_compatible;
    } test_cases[] = {
        // Shared locks are compatible with each other
        {LOCK_SHARED, LOCK_SHARED, true},
        {LOCK_SHARED, LOCK_INTENT_SHARED, true},
        {LOCK_SHARED, LOCK_INTENT_EXCLUSIVE, true},
        {LOCK_SHARED, LOCK_SHARED_IX, true},
        {LOCK_SHARED, LOCK_UPDATE, true},
        
        // Exclusive locks are incompatible with everything
        {LOCK_EXCLUSIVE, LOCK_SHARED, false},
        {LOCK_EXCLUSIVE, LOCK_EXCLUSIVE, false},
        {LOCK_EXCLUSIVE, LOCK_INTENT_SHARED, false},
        {LOCK_EXCLUSIVE, LOCK_INTENT_EXCLUSIVE, false},
        {LOCK_EXCLUSIVE, LOCK_SHARED_IX, false},
        {LOCK_EXCLUSIVE, LOCK_UPDATE, false},
        
        // Intent shared locks
        {LOCK_INTENT_SHARED, LOCK_SHARED, true},
        {LOCK_INTENT_SHARED, LOCK_EXCLUSIVE, false},
        {LOCK_INTENT_SHARED, LOCK_INTENT_SHARED, true},
        {LOCK_INTENT_SHARED, LOCK_INTENT_EXCLUSIVE, true},
        {LOCK_INTENT_SHARED, LOCK_SHARED_IX, true},
        {LOCK_INTENT_SHARED, LOCK_UPDATE, false},
        
        // Intent exclusive locks
        {LOCK_INTENT_EXCLUSIVE, LOCK_SHARED, true},
        {LOCK_INTENT_EXCLUSIVE, LOCK_EXCLUSIVE, false},
        {LOCK_INTENT_EXCLUSIVE, LOCK_INTENT_SHARED, true},
        {LOCK_INTENT_EXCLUSIVE, LOCK_INTENT_EXCLUSIVE, true},
        {LOCK_INTENT_EXCLUSIVE, LOCK_SHARED_IX, false},
        {LOCK_INTENT_EXCLUSIVE, LOCK_UPDATE, false}
    };
    
    size_t num_cases = sizeof(test_cases) / sizeof(test_cases[0]);
    for (size_t i = 0; i < num_cases; i++) {
        bool result = locks_compatible(test_cases[i].mode1, test_cases[i].mode2);
        assert(result == test_cases[i].expected_compatible);
        
        if (i % 10 == 0) {
            printf("Tested %zu compatibility cases...\n", i + 1);
        }
    }
    
    printf("Lock compatibility matrix test passed (%zu cases)!\n", num_cases);
}

void test_lock_acquisition_and_waiting() {
    printf("Testing lock acquisition and waiting...\n");
    
    // Create two transactions
    transaction_t* txn1 = txn_begin(g_tm, ISOLATION_READ_COMMITTED);
    transaction_t* txn2 = txn_begin(g_tm, ISOLATION_READ_COMMITTED);
    assert(txn1 != NULL && txn2 != NULL);
    
    printf("Testing with transactions %llu and %llu\n", txn1->xid, txn2->xid);
    
    // Transaction 1 acquires an exclusive lock
    int result = txn_acquire_lock_with_wait(g_tm, txn1, LOCK_EXCLUSIVE, LOCK_TUPLE,
                                          1, 0, 100, 1000);
    assert(result == 0);
    printf("Transaction %llu acquired exclusive lock\n", txn1->xid);
    
    // Transaction 2 tries to acquire conflicting lock (should fail quickly for test)
    result = txn_acquire_lock_with_wait(g_tm, txn2, LOCK_EXCLUSIVE, LOCK_TUPLE,
                                      1, 0, 100, 100); // Short timeout
    printf("Transaction %llu lock acquisition result: %d\n", txn2->xid, result);
    // Should fail with timeout (-3) or deadlock (-2)
    assert(result < 0);
    
    // Transaction 2 should be able to acquire a shared lock on different resource
    result = txn_acquire_lock_with_wait(g_tm, txn2, LOCK_SHARED, LOCK_TUPLE,
                                      1, 0, 101, 1000);
    assert(result == 0);
    printf("Transaction %llu acquired shared lock on different resource\n", txn2->xid);
    
    // Clean up
    txn_commit(g_tm, txn1);
    txn_commit(g_tm, txn2);
    
    printf("Lock acquisition and waiting test passed!\n");
}

void test_deadlock_detection() {
    printf("Testing deadlock detection...\n");
    
    // Create wait-for graph for testing
    wait_for_graph_t* graph = wait_graph_create(10);
    assert(graph != NULL);
    
    // Add edges to create a cycle: 1->2->3->1
    assert(wait_graph_add_edge(graph, 1, 2) == 0);
    assert(wait_graph_add_edge(graph, 2, 3) == 0);
    assert(wait_graph_add_edge(graph, 3, 1) == 0);
    
    // Test cycle detection
    bool has_cycle = wait_graph_has_cycle(graph, 1);
    assert(has_cycle == true);
    printf("Cycle detection working correctly\n");
    
    // Test victim selection
    txn_id_t cycle_nodes[] = {1, 2, 3};
    txn_id_t victim = wait_graph_select_victim(graph, cycle_nodes, 3);
    assert(victim == 3); // Should select highest ID
    printf("Victim selection: chose transaction %llu\n", victim);
    
    // Remove edge to break cycle
    wait_graph_remove_edge(graph, 3, 1);
    has_cycle = wait_graph_has_cycle(graph, 1);
    assert(has_cycle == false);
    printf("Cycle successfully removed\n");
    
    wait_graph_destroy(graph);
    printf("Deadlock detection test passed!\n");
}

void test_undo_log_management() {
    printf("Testing undo log management...\n");
    
    transaction_t* txn = txn_begin(g_tm, ISOLATION_READ_COMMITTED);
    assert(txn != NULL);
    
    // Test adding undo log entries
    char data1[] = "original data 1";
    char data2[] = "original data 2";
    char data3[] = "original data 3";
    
    int result = txn_add_undo_log(txn, UNDO_INSERT, 1, 100, NULL, 0, 200);
    assert(result == 0);
    
    result = txn_add_undo_log(txn, UNDO_UPDATE, 1, 101, data1, sizeof(data1), 201);
    assert(result == 0);
    
    result = txn_add_undo_log(txn, UNDO_DELETE, 1, 102, data2, sizeof(data2), 202);
    assert(result == 0);
    
    assert(txn->undo_log_count == 3);
    printf("Added 3 undo log entries\n");
    
    // Test applying undo log (this is mostly a structure test since we don't have storage engine)
    result = txn_apply_undo_log(g_tm, txn);
    assert(result == 0);
    printf("Applied undo log successfully\n");
    
    // Test cleanup
    txn_clear_undo_log(txn);
    assert(txn->undo_log_count == 0);
    assert(txn->undo_log == NULL);
    printf("Cleared undo log successfully\n");
    
    txn_commit(g_tm, txn);
    printf("Undo log management test passed!\n");
}

typedef struct {
    int thread_id;
    int num_transactions;
    int* results;
} thread_test_data_t;

void* concurrent_transaction_thread(void* arg) {
    thread_test_data_t* data = (thread_test_data_t*)arg;
    
    for (int i = 0; i < data->num_transactions; i++) {
        transaction_t* txn = txn_begin(g_tm, ISOLATION_READ_COMMITTED);
        if (!txn) {
            data->results[i] = -1;
            continue;
        }
        
        // Try to acquire some locks
        int lock_result = txn_acquire_lock_with_wait(g_tm, txn, LOCK_SHARED, LOCK_TUPLE,
                                                   data->thread_id, 0, i, 1000);
        
        // Simulate some work
        usleep(rand() % 10000); // 0-10ms
        
        // Commit or abort based on lock result
        int txn_result;
        if (lock_result == 0) {
            txn_result = txn_commit(g_tm, txn);
        } else {
            txn_result = txn_abort(g_tm, txn);
        }
        
        data->results[i] = (lock_result == 0 && txn_result == 0) ? 0 : -1;
    }
    
    return NULL;
}

void test_concurrent_transactions() {
    printf("Testing concurrent transactions...\n");
    
    pthread_t threads[NUM_TEST_THREADS];
    thread_test_data_t thread_data[NUM_TEST_THREADS];
    int results[NUM_TEST_THREADS][NUM_TRANSACTIONS_PER_THREAD];
    
    // Initialize thread data
    for (int i = 0; i < NUM_TEST_THREADS; i++) {
        thread_data[i].thread_id = i;
        thread_data[i].num_transactions = NUM_TRANSACTIONS_PER_THREAD;
        thread_data[i].results = results[i];
    }
    
    // Start threads
    for (int i = 0; i < NUM_TEST_THREADS; i++) {
        int result = pthread_create(&threads[i], NULL, 
                                   concurrent_transaction_thread, &thread_data[i]);
        assert(result == 0);
    }
    
    // Wait for threads to complete
    for (int i = 0; i < NUM_TEST_THREADS; i++) {
        pthread_join(threads[i], NULL);
    }
    
    // Analyze results
    int total_transactions = 0;
    int successful_transactions = 0;
    
    for (int i = 0; i < NUM_TEST_THREADS; i++) {
        for (int j = 0; j < NUM_TRANSACTIONS_PER_THREAD; j++) {
            total_transactions++;
            if (results[i][j] == 0) {
                successful_transactions++;
            }
        }
    }
    
    printf("Concurrent test results: %d/%d transactions successful (%.1f%%)\n",
           successful_transactions, total_transactions,
           (100.0 * successful_transactions) / total_transactions);
    
    // Print transaction manager statistics
    txn_manager_print_stats(g_tm);
    
    printf("Concurrent transactions test completed!\n");
}

void test_isolation_levels() {
    printf("Testing isolation level configuration...\n");
    
    // Test different isolation levels
    isolation_level_t levels[] = {
        ISOLATION_READ_UNCOMMITTED,
        ISOLATION_READ_COMMITTED,
        ISOLATION_REPEATABLE_READ,
        ISOLATION_SERIALIZABLE
    };
    
    for (size_t i = 0; i < sizeof(levels) / sizeof(levels[0]); i++) {
        transaction_t* txn = txn_begin(g_tm, levels[i]);
        assert(txn != NULL);
        assert(txn->isolation_level == levels[i]);
        
        printf("Created transaction with isolation level: %s\n",
               isolation_level_to_string(levels[i]));
        
        txn_commit(g_tm, txn);
    }
    
    printf("Isolation level configuration test passed!\n");
}

int main() {
    printf("=== GuDB Transaction ACID Features Test ===\n\n");
    
    // Seed random number generator
    srand((unsigned int)time(NULL));
    
    setup_test_environment();
    
    test_transaction_lifecycle();
    printf("\n");
    
    test_transaction_abort();
    printf("\n");
    
    test_lock_compatibility_matrix();
    printf("\n");
    
    test_lock_acquisition_and_waiting();
    printf("\n");
    
    test_deadlock_detection();
    printf("\n");
    
    test_undo_log_management();
    printf("\n");
    
    test_isolation_levels();
    printf("\n");
    
    test_concurrent_transactions();
    printf("\n");
    
    cleanup_test_environment();
    
    printf("=== All ACID features tests completed successfully! ===\n");
    printf("Check the log file: test_transaction_acid.log for detailed logs\n");
    
    return 0;
}